import tkinter as tk  # tkinter库用于创建用户界面
import os # os库用于操作文件和目录
import pandas as pd  # pandas库用于数据处理
from sklearn.neighbors import KNeighborsClassifier  # sklearn库中的KNeighborsClassifier用于实现KNN算法
import numpy as np  # numpy库用于进行数值计算
import csv  # csv库用于读写csv文件
from PIL import Image, ImageTk  # PIL库用于处理图片
import logging  # logging库用于记录日志
import matplotlib.pyplot as plt  # matplotlib库用于数据可视化
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg  # 在tkinter中显示matplotlib图像
logging.basicConfig(level=logging.INFO, format='%(asctime)s:%(levelname)s:%(message)s')  # 日志配置

class MovieRatingApp:  # 定义电影评分应用类
    def __init__(self, root):  # 初始化方法，设置应用的基本属性和界面
        self.root = root  # 根窗口
        root.title("Movie Rating App")  # 窗口标题
        root.geometry("900x600")  # 窗口大小

        # 加载并显示电影图片（注意：确保图片路径正确，若图片不存在需修改路径）
        self.image1 = Image.open(os.path.join(os.path.dirname(__file__), 'post1_relie.jpg'))
        self.image1.thumbnail((100, 300))  # 缩放图片
        self.movie1_image = ImageTk.PhotoImage(self.image1)
        self.movie1_label = tk.Label(root, image=self.movie1_image)
        self.movie1_label.grid(row=0, column=0, padx=10, pady=10)

        self.image2 = Image.open(os.path.join(os.path.dirname(__file__), "post2_family.jpg"))
        self.image2.thumbnail((100, 300))
        self.movie2_image = ImageTk.PhotoImage(self.image2)
        self.movie2_label = tk.Label(root, image=self.movie2_image)
        self.movie2_label.grid(row=0, column=1, padx=10, pady=10)

        # 评分下拉菜单
        self.movie1_rating = tk.StringVar(root, value="1")
        tk.OptionMenu(root, self.movie1_rating, "1", "2", "3", "4", "5").grid(row=1, column=0, padx=10, pady=10)

        self.movie2_rating = tk.StringVar(root, value="1")
        tk.OptionMenu(root, self.movie2_rating, "1", "2", "3", "4", "5").grid(row=1, column=1, padx=10, pady=10)

        # 电影类型单选按钮
        self.preference = tk.IntVar()
        tk.Radiobutton(root, text="动作片", variable=self.preference, value=0).grid(row=2, column=0, padx=10, pady=10)
        tk.Radiobutton(root, text="喜剧片", variable=self.preference, value=1).grid(row=2, column=1, padx=10, pady=10)

        # 功能按钮
        tk.Button(root, text="Confirm", command=self.confirm).grid(row=3, column=0, padx=10, pady=20)
        tk.Button(root, text="Clear", command=self.clear_plot).grid(row=3, column=1, padx=10, pady=20)
        tk.Button(root, text="Predict", command=self.predict_preference).grid(row=4, columnspan=2, padx=10, pady=20)
        self.predict_label = tk.Label(root, text="")  # 预测结果标签
        self.predict_label.grid(row=5, columnspan=2, padx=10, pady=10)

        # 数据文件路径（自动创建ratings.csv）
        self.filename = os.path.join(os.path.dirname(__file__), 'ratings.csv')
        if not os.path.isfile(self.filename):
            with open(self.filename, 'w', newline='') as file:
                writer = csv.writer(file)
                writer.writerow(["Movie1", "Movie2", "Preference"])  # 表头

        # 绘图框架（用于显示matplotlib图像）
        self.plot_frame = tk.Frame(root)
        self.plot_frame.grid(row=0, column=2, rowspan=4, padx=10, pady=10)
        self.plot_ratings()  # 初始绘图

    def confirm(self):  # 保存用户评分和类型选择
        m1_rating = self.movie1_rating.get()
        m2_rating = self.movie2_rating.get()
        preference = self.preference.get()

        # 追加数据到CSV
        with open(self.filename, 'a', newline='') as file:
            writer = csv.writer(file)
            writer.writerow([m1_rating, m2_rating, preference])

        self.plot_ratings()  # 更新绘图

    def clear_plot(self):  # 清空CSV数据并重置绘图
        with open(self.filename, 'w', newline='') as file:
            writer = csv.writer(file)
            writer.writerow(["Movie1", "Movie2", "Preference"])  # 重写表头
        self.predict_label.config(text="")  # 清空预测结果
        self.plot_ratings()  # 重置绘图

    def plot_ratings(self):  # 绘制评分数据散点图（无预测点）
        self.plot_frame.destroy()  # 销毁旧框架（避免图像重叠）
        self.plot_frame = tk.Frame(self.root)
        self.plot_frame.grid(row=0, column=2, rowspan=4, padx=10, pady=10)

        # 读取CSV数据（处理空文件情况）
        df = pd.read_csv(self.filename)
        if len(df) <= 1:  # 仅表头时不绘图
            fig, ax = plt.subplots(figsize=(6, 8))
            ax.set_xlabel('Movie A Rating')
            ax.set_ylabel('Movie B Rating')
            ax.set_xlim([0, 6])
            ax.set_ylim([0, 6])
            ax.grid(True)
            ax.legend()  # 避免无数据时图例报错
        else:
            # 筛选动作片（0）和喜剧片（1）数据
            action_movies = df[df['Preference'] == 0]
            comedy_movies = df[df['Preference'] == 1]

            # 创建图像
            fig, ax = plt.subplots(figsize=(6, 8))
            ax.grid(True)
            # 绘制两类数据散点图
            ax.scatter(action_movies['Movie1'], action_movies['Movie2'], color='orange', label='Action')
            ax.scatter(comedy_movies['Movie1'], comedy_movies['Movie2'], color='blue', label='Comedy')
            # 添加数据点坐标标签
            for i in range(len(action_movies)):
                ax.text(
                    action_movies['Movie1'].iloc[i], 
                    action_movies['Movie2'].iloc[i], 
                    f'({action_movies["Movie1"].iloc[i]}, {action_movies["Movie2"].iloc[i]})', 
                    fontsize=8
                )
            for i in range(len(comedy_movies)):
                ax.text(
                    comedy_movies['Movie1'].iloc[i], 
                    comedy_movies['Movie2'].iloc[i], 
                    f'({comedy_movies["Movie1"].iloc[i]}, {comedy_movies["Movie2"].iloc[i]})', 
                    fontsize=8
                )
            # 坐标轴配置
            ax.set_xlabel('Movie A Rating')
            ax.set_ylabel('Movie B Rating')
            ax.set_xlim([0, 6])
            ax.set_ylim([0, 6])
            ax.legend()

        # 在tkinter框架中显示图像
        canvas = FigureCanvasTkAgg(fig, master=self.plot_frame)
        canvas.draw()
        canvas.get_tk_widget().pack()

    def predict_preference(self):  # KNN预测用户喜欢的电影类型
        # 读取数据并检查是否有足够样本（至少1条有效数据）
        df = pd.read_csv(self.filename)
        if len(df) <= 1:
            self.predict_label.config(text="数据不足，无法预测！请先通过Confirm添加评分数据")
            return

        # 准备训练数据（特征：Movie1、Movie2评分；标签：Preference类型）
        X = df[['Movie1', 'Movie2']].values.astype(int)  # 转为int（避免字符串类型报错）
        y = df['Preference'].values.astype(int)

        # 训练KNN模型（k=1，即最近邻）
        knn = KNeighborsClassifier(n_neighbors=1)
        knn.fit(X, y)

        # 获取当前用户的评分（转为int）
        m1_rating = int(self.movie1_rating.get())
        m2_rating = int(self.movie2_rating.get())
        new_point = np.array([[m1_rating, m2_rating]])  # 预测数据点

        # 执行预测并获取最近邻
        prediction = knn.predict(new_point)
        distances, indices = knn.kneighbors(new_point, n_neighbors=1, return_distance=True)
        nearest_neighbor = X[indices[0][0]]  # 最近邻的坐标

        # 显示预测结果
        self.predict_label.config(
            text=f"给用户推荐的电影类型: {'动作片' if prediction[0] == 0 else '喜剧片'}\n"
                 f"最近邻数据点: ({nearest_neighbor[0]}, {nearest_neighbor[1]})"
        )

        # 重新绘图（包含预测点和最近邻连线）
        self.plot_frame.destroy()
        self.plot_frame = tk.Frame(self.root)
        self.plot_frame.grid(row=0, column=2, rowspan=4, padx=10, pady=10)

        # 筛选数据并绘图
        action_movies = df[df['Preference'] == 0]
        comedy_movies = df[df['Preference'] == 1]
        fig, ax = plt.subplots(figsize=(6, 8))
        ax.grid(True)
        # 原有数据点
        ax.scatter(action_movies['Movie1'], action_movies['Movie2'], color='orange', label='Action')
        ax.scatter(comedy_movies['Movie1'], comedy_movies['Movie2'], color='blue', label='Comedy')
        # 预测点（红色X标记）
        ax.scatter(new_point[:, 0], new_point[:, 1], color='red', marker='x', s=100, label='Your Rating')
        # 最近邻连线（红色虚线）
        ax.plot(
            [new_point[0, 0], nearest_neighbor[0]], 
            [new_point[0, 1], nearest_neighbor[1]], 
            'r--', label='Nearest Neighbor'
        )
        # 坐标标签
        for i in range(len(action_movies)):
            ax.text(
                action_movies['Movie1'].iloc[i], 
                action_movies['Movie2'].iloc[i], 
                f'({action_movies["Movie1"].iloc[i]}, {action_movies["Movie2"].iloc[i]})', 
                fontsize=8
            )
        for i in range(len(comedy_movies)):
            ax.text(
                comedy_movies['Movie1'].iloc[i], 
                comedy_movies['Movie2'].iloc[i], 
                f'({comedy_movies["Movie1"].iloc[i]}, {comedy_movies["Movie2"].iloc[i]})', 
                fontsize=8
            )
        # 坐标轴配置
        ax.set_xlabel('Movie A Rating')
        ax.set_ylabel('Movie B Rating')
        ax.set_xlim([0, 6])
        ax.set_ylim([0, 6])
        ax.legend()

        # 在tkinter中显示更新后的图像
        canvas = FigureCanvasTkAgg(fig, master=self.plot_frame)
        canvas.draw()
        canvas.get_tk_widget().pack()

# 启动应用
if __name__ == "__main__":  # 规范代码：确保主程序入口正确
    root = tk.Tk()
    app = MovieRatingApp(root)
    root.mainloop()